<?php

declare(ENCODING = 'UTF-8');

/*                                                                        *
 * This script is part of the TYPO3 project - inspiring people to share!  *
 *                                                                        *
 * TYPO3 is free software; you can redistribute it and/or modify it under *
 * the terms of the GNU General Public License version 2 as published by  *
 * the Free Software Foundation.                                          *
 *                                                                        *
 * This script is distributed in the hope that it will be useful, but     *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *	
 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General      *
 * Public License for more details.                                       *
 *                                                                        */	

/**
 * @package auxo
 * @subpackage AOP
 * @version $Id$
 */

/**	
 * The AOP XML Aspect Parser
 *	
 * @package auxo
 * @subpackage AOP
 * @version $Id$	
 * @copyright Copyright belongs to the respective authors
 * @author andreas.horn@extronaut.de
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License, version 2
 */
class tx_auxo_AOPPointcutExpressionParser extends tx_auxo_AOPPointcutExpression {
	/**
	 * Operation codes
	 *
	 */
	const 	OP_AND = 1;
	const	OP_OR = 2;
	const	OP_NOT = 3;
	const	OP_CALL = 4;
	const	OP_SET = 5;
	const 	OP_GET = 6;
	
	/**
	 * A tokenizer that splits an input string and offers methods to get tokens
	 *
	 * @var tx_auxo_AOPPointcutExpressionTokenizer
	 */
	private $tokenizer = NULL;
	
	/**
	 * A parsed pointcut runtime object
	 * 
	 * @var tx_auxo_AOPPointcutRuntime
	 */
	private $runtime = NULL;
	
	/**
	 * Parse a string and returns a pointcut expression 
	 *
	 * @param string $input
	 * @return tx_auxo_AOPPointcutExpression $expression
	 */
	public function parse($input) {		
		$this->tokenizer = new tx_auxo_AOPPointcutExpressionTokenizer($input);
		$this->runtime = new tx_auxo_AOPPointcutRuntime();
		$this->parseDesignatorExpression();
		return $this->runtime;
	}

	/**
	 * Parses a pointcut designator
	 * 
	 * @throws tx_auxo_AOPPointcutExpressionParserException 
	 */
	private function parseDesignatorExpression() {
		switch ($this->tokenizer->get()) {
			case self::TOKEN_OPEN_BRACKET:		
				$this->parseDesignatorExpression();
				$this->expect(self::TOKEN_CLOSE_BRACKET, 'Closing bracket expected');
				$this->tokenizer->next();
				break;
			case self::TOKEN_EXECUTE:
			case self::TOKEN_WITHIN:
				$identifier = $this->tokenizer->identifier();
				$this->tokenizer->next();
				$this->expect(self::TOKEN_OPEN_BRACKET, '"(" expected after execute/within');
				$this->tokenizer->next();
				$this->parsePointcutExpression();			
				$this->expect(self::TOKEN_CLOSE_BRACKET, sprintf('")" expected at end of execute/within instead of %s', $this->tokenizer->identifier()));
				$this->tokenizer->next();	
				$this->runtime->addCodeArray(array($identifier, self::OP_CALL));
				break;					
			case self::TOKEN_AND:
				$this->tokenizer->next();
				$this->parseDesignatorExpression();
				$this->runtime->addCode(self::OP_AND);		
				break;
			case self::TOKEN_OR:
				$this->tokenizer->next();
				$this->parseDesignatorExpression();
				$this->runtime->addCode(self::OP_OR);						
				break;
			case self::TOKEN_NOT:
				$this->tokenizer->next();
				$this->parseDesignatorExpression();	
				$this->runtime->addCode(self::OP_NOT);
				break;
			default:
				throw new tx_auxo_AOPPointcutExpressionParserException(sprintf('unknown token %s', $this->tokenizer->identifier()));
		}
	}
	
	/**
	 * Parse a pointcut expression string
	 *
	 * @return tx_auxo_AOPPointcutExpression $expression
	 */
	private function parsePointcutExpression() {	
		switch ($this->tokenizer->get()) {
			case self::TOKEN_OPEN_BRACKET:
				$this->parsePointcutExpression();
				$this->expect(self::TOKEN_CLOSE_BRACKET, 'Closing bracket is missing');
				$this->tokenizer->next();
				break;
			case self::TOKEN_AND:
				$this->tokenizer->next();
				$this->parsePointcutExpression();
				$this->runtime->addCode(self::OP_AND);		
				break;
			case self::TOKEN_OR:
				$this->tokenizer->next();
				$this->parsePointcutExpression();
				$this->runtime->addCode(self::OP_OR);						
				break;
			case self::TOKEN_NOT:
				$this->tokenizer->next();
				$this->parsePointcutExpression();	
				$this->runtime->addCode(self::OP_NOT);
				break;
			case self::TOKEN_PUBLIC:
			case self::TOKEN_PROTECTED:			
				$this->parsePointcutDeclaration();
				$this->runtime->addCodeArray(array('match', self::OP_CALL));
				break;
			default:
				$this->parsePointcutExpression();
		}
	}
	
	/**
	 * Parse a pointcut declaration
	 * 
	 * <modifier> <class->method> <signature>
	 *
	 */
	private function parsePointcutDeclaration() {
		switch($this->tokenizer->get()) {
			case self::TOKEN_PUBLIC:	
			case self::TOKEN_PROTECTED:
				$this->runtime->addCodeArray(array($this->tokenizer->identifier(), 'visibility', self::OP_SET));
				$this->tokenizer->next();								
				break;
			default:
				$this->runtime->addCodeArray(array('*', 'visibility', self::OP_SET));	
				$this->tokenizer->next();	
		}
		$this->parseClassMethodIdentifier();
		$this->parseSignature();	
	}

	/**
	 * Parse a pointcut class->method identifier
	 *
	 */
	private function parseClassMethodIdentifier() {
		if ($this->tokenizer->get(1) == self::TOKEN_CLASS_REF) {			
			$this->runtime->addCodeArray(array($this->tokenizer->identifier(), 'class', self::OP_SET));
			$this->tokenizer->next(2);												
		}
		else {
			$this->runtime->addCodeArray(array('*', 'class', self::OP_SET));
		}
		
		if ($this->tokenizer->get() == self::TOKEN_IDENTIFIER) {
			$this->runtime->addCodeArray(array($this->tokenizer->identifier(),'method', self::OP_SET));
			$this->tokenizer->next();
		}
		else {
			$this->runtime->addCodeArray(array('*', 'method', self::OP_SET));
		}
	}
	
	/**
	 * Parse a pointcut method signature
	 * 
	 * @throws tx_auxo_AOPPointcutExpressionParserException 
	 */
	private function parseSignature() {
		if ($this->tokenizer->get() <> self::TOKEN_OPEN_BRACKET) {
			return void;
		}
		
		$this->expect(self::TOKEN_OPEN_BRACKET, 'Open Bracket for signature expected');
		$this->tokenizer->next();

		if ($this->tokenizer->get() == self::TOKEN_DOT_DOT) {
			if ($this->tokenizer->get(1) <> self::TOKEN_CLOSE_BRACKET) {
				$this->runtime->addCodeArray(array('..', 'type', self::OP_SET));				
				$this->runtime->addCodeArray(array('..', 'parameter', self::OP_SET));	
				throw new tx_auxo_AOPPointcutExpressionParserException('Closing bracket after .. expected');
			}
			$this->tokenizer->next();
			return void;
		}
		
		if ($this->tokenizer->get() == self::TOKEN_CLOSE_BRACKET) {
			$this->tokenizer->next();			
			return void;
		}

		do {
			if ($this->tokenizer->get(0) == self::TOKEN_IDENTIFIER &&
			    $this->tokenizer->get(1) == self::TOKEN_VARIABLE) {
				$this->runtime->addCodeArray(array($this->tokenizer->identifier(), 'type', self::OP_SET));
				$this->tokenizer->next();
			}
			else {
				$this->runtime->addCodeArray(array('*', 'type', self::OP_SET));
			}
			
			$this->expect(self::TOKEN_VARIABLE, 'variable as method parameter expected');			
			$this->runtime->addCodeArray(array($this->tokenizer->identifier(), 'parameter', self::OP_SET));
			$this->tokenizer->next();

			switch($this->tokenizer->get()) {
				case self::TOKEN_COMMA:
					break;
				case self::TOKEN_CLOSE_BRACKET:
					$end = true;
					break;
				default:
					throw new tx_auxo_AOPPointcutExpressionParserException('unexpected token in signature');					
			}		
			$this->tokenizer->next();	
		}
		while(! $end);		
	}
	
	/**
	 * Lookups and compares the next token with an expected token
	 *
	 * @param int $token
	 * @param string $message
	 * @throws tx_auxo_AOPPointcutExpressionParserException 
	 */
	private function expect($token, $message='') {
		if ($this->tokenizer->get() <> $token) {
			throw new tx_auxo_AOPPointcutExpressionParserException($message);			
		}
	}
}
?>